package scoping.helpers;

import ic.ast.decl.DeclClass;
import ic.ast.decl.DeclField;
import ic.ast.decl.DeclLibraryMethod;
import ic.ast.decl.DeclMethod;
import ic.ast.decl.DeclStaticMethod;
import ic.ast.decl.DeclVirtualMethod;
import ic.ast.decl.Parameter;
import ic.ast.stmt.LocalVariable;
import scoping.ScopeTree;
import scoping.blocks.ClassScope;
import scoping.blocks.GlobalScope;
import scoping.blocks.MethodScope;
import scoping.blocks.ScopeBlock;
import scoping.blocks.StmtBlockScope;

public class SemanticCheckerResponse {
	private ScopeTree _scopeTree;
	private String _errorMessage;
	private boolean _isValid;
	
	
	public SemanticCheckerResponse(ScopeTree _scopeTree, String _errorMessage,	boolean _isValid) {
		this._scopeTree = _scopeTree;
		this._errorMessage = _errorMessage;
		this._isValid = _isValid;
	}
	
	public ScopeTree get_scopeTree() {
		return _scopeTree;
	}
	public String get_errorMessage() {
		return _errorMessage;
	}
	public boolean is_isValid() {
		return _isValid;
	}

	public String printScopeTree() throws SemanticCheckerExcpetion {
		return printScopeTree(_scopeTree).replaceAll("\\s+$", "");
	}
	
	private String printScopeTree(ScopeTree tree) throws SemanticCheckerExcpetion {
		StringBuilder sb = new StringBuilder();
		sb.append(printScopeBlock(tree));
		for (ScopeTree child:tree.getChildren()){
			sb.append(printScopeTree(child));
		}
		return sb.toString();
	}
	
	private String printScopeBlock(ScopeTree tree) throws SemanticCheckerExcpetion {
		ScopeBlock scope = tree.getBlock();
		if (scope instanceof GlobalScope){
			return printScope((GlobalScope)scope);
		}else if (scope instanceof ClassScope){
			return printScope((ClassScope)scope);
		}else if (scope instanceof MethodScope){
			return printScope((MethodScope)scope, tree);
		}else if (scope instanceof StmtBlockScope){
			return printScope((StmtBlockScope)scope, tree);
		} 
		return "";
	}
	
	private String printScope(GlobalScope scope){
		StringBuilder sb = new StringBuilder();
		sb.append("\n");
		sb.append("Global Symbol Table\n");
		if (scope.hasLibrary()){
			sb.append(String.format("    Class:  %s\n", scope.getLibraryClass().getName()));
		}
		for (String className:scope.getClassNames()){
			sb.append(String.format("    Class:  %s\n", className));
		}
		sb.append("\n");
		return sb.toString();
	}
	
	private String printScope(ClassScope scope) throws SemanticCheckerExcpetion{
		StringBuilder sb = new StringBuilder();
		DeclClass declClass = scope.getClassDeclaration();
		String parentString = scope.getClassDeclaration().hasSuperClass() ? 
				String.format("  (parent = %s)", scope.getClassDeclaration().getSuperClassName()) : 
					"";
		sb.append(String.format("Class Symbol Table: %s%s\n", declClass.getName(), parentString));
		for (DeclField declField:declClass.getFields()){
			sb.append(String.format("    Field:  %s : %s", declField.getName(), declField.getType().getDisplayName()));
			for (int j=0;j<declField.getType().getArrayDimension();j++){
				sb.append("[]");
			}
			sb.append("\n");
		}
		for (DeclMethod declMethod:declClass.getMethods()) {
			if (declMethod instanceof DeclStaticMethod || declMethod instanceof DeclLibraryMethod){
				sb.append(String.format("    Static method:  %s : ", declMethod.getName()));
			} else if (declMethod instanceof DeclVirtualMethod){
				sb.append(String.format("    Virtual method:  %s : ", declMethod.getName()));
			} else {
				throw new SemanticCheckerExcpetion(String.format("Error in print of class scope: method %s not static, virtual or library", declMethod.getName()));
			}
			if (declMethod.getFormals().size() > 0){
				sb.append(declMethod.getFormals().get(0).getType().getDisplayName());
				for (int j=0;j<declMethod.getFormals().get(0).getType().getArrayDimension();j++){
					sb.append("[]");
				}
				for (int i=1;i<declMethod.getFormals().size();i++){
					sb.append(", " + declMethod.getFormals().get(i).getType().getDisplayName());
					for (int j=0;j<declMethod.getFormals().get(i).getType().getArrayDimension();j++){
						sb.append("[]");
					}
				}
			}
			sb.append(String.format(" -> %s", declMethod.getType().getDisplayName()));
			for (int j=0;j<declMethod.getType().getArrayDimension();j++){
				sb.append("[]");
			}
			sb.append("\n");
		}
		sb.append("\n");
		return sb.toString();
	}
	
	private String printScope(MethodScope scope, ScopeTree tree) throws SemanticCheckerExcpetion{
		StringBuilder sb = new StringBuilder();
		ScopeBlock parentScope = tree.getParent().getBlock();
		if (!(parentScope instanceof ClassScope)){
			throw new SemanticCheckerExcpetion(String.format("Error in print of method scope {0}. Method's parent scope is not class"));
		}
		sb.append(String.format("Method Symbol Table: %s  (parent = %s)\n", scope.getMethodDeclaration().getName(), ((ClassScope)parentScope).getClassDeclaration().getName()));
		for (Parameter param:scope.getParams()){
			sb.append(String.format("    Parameter:  %s : %s", param.getName(), param.getType().getDisplayName()));
			for (int j=0;j<param.getType().getArrayDimension();j++){
				sb.append("[]");
			}
			sb.append("\n");
		}
		for (LocalVariable variable:scope.getLocalVars()){
			sb.append(String.format("    Local variable:  %s : %s", variable.getName(), variable.getType().getDisplayName()));
			for (int j=0;j<variable.getType().getArrayDimension();j++){
				sb.append("[]");
			}
			sb.append("\n");
		}
		sb.append("\n");
		return sb.toString();
	}
	
	private String printScope(StmtBlockScope scope, ScopeTree tree) throws SemanticCheckerExcpetion{
		StringBuilder sb = new StringBuilder();
		DeclMethod declMethod = null;
		if (scope.getIsOneLiner()){
			return "";
		}
		boolean methodScopeDirectChild = tree.getParent().getBlock() instanceof MethodScope;
		while (tree != null){
			if (tree.getBlock() instanceof MethodScope){
				declMethod = ((MethodScope)tree.getBlock()).getMethodDeclaration();
			}
			tree = tree.getParent();
		}
		if (declMethod == null){
			throw new SemanticCheckerExcpetion("Could not find stmt block scope parent method");
		}
		sb.append(String.format("Statement Block Symbol Table: @%s  (parent = %s%s)\n", declMethod.getName(), methodScopeDirectChild ? "" : "@", declMethod.getName()));
		for (LocalVariable variable:scope.getLocalVars()){
			sb.append(String.format("    Local variable:  %s : %s", variable.getName(), variable.getType().getDisplayName()));
			for (int j=0;j<variable.getType().getArrayDimension();j++){
				sb.append("[]");
			}
			sb.append("\n");			
		}
		sb.append("\n");
		return sb.toString();
	}
	
}
